const Service = require('egg').Service;
const _ = require('lodash');
const moment = require('moment');
const cheerio = require('cheerio');
const queue = require('async/queue');


class IResearchService extends Service {
    // 默认不需要提供构造函数。
    // constructor(ctx) {
    //     super(ctx);
    //     /*    如果需要在构造函数做一些处理，一定要有这句话，才能保证后面 `this.ctx`的使用。
    //         就可以直接通过 this.ctx 获取 ctx 了
    //         还可以直接通过 this.app 获取 app 了*/
    // }

    /**
     *
     * @param platform 采集的平台
     * @param date 采集的日期
     */
    async get(platform, date) {
        if (!(platform.id > 0))
            throw new Error('无效的平台数据');
        date = moment(date);
        if (!date.isValid())
            throw new Error('提供了无法解析的日期');

        date = moment(date).format('YYYY-MM-DD');

        console.log(`开始采集 ${platform.name} ${date}`);

        const {DailyCategory, DailyCategoryData, DailyPlatformCategory} = this.app.model;
        const ctx = this.ctx;

        let categories = {}, data, associate = {};
        //采集分类，读取分类，整理分类
        {
            //采集分类
            let res = await getCategories(ctx, date, platform);
            if (_.isEmpty(res))
                throw new Error('采集不到数据');
            data = res.data;
            let newCategories = Object.values(res.categories);
            let nowCategories = await DailyCategory.findAll({raw: true});
            //整理所有分类
            nowCategories.forEach(item => {
                categories[item.name.toLowerCase()] = item;
            });
            newCategories = newCategories.filter(category => {
                return !categories.hasOwnProperty(category.name.toLowerCase());
            });
            console.log('需要插入到数据库的新分类：', newCategories.length);
            let platformCategories;
            if (newCategories.length > 0) {
                await DailyCategory.bulkCreate(newCategories, {updateOnDuplicate: ['name']});
                [nowCategories, platformCategories] = await Promise.all([
                    DailyCategory.findAll({raw: true}),
                    DailyPlatformCategory.findAll({where: {platform_id: platform.id}})
                ]);
                //整理所有分类
                nowCategories.forEach(item => {
                    categories[item.name.toLowerCase()] = item;
                });
            } else {
                platformCategories = await DailyPlatformCategory.findAll({where: {platform_id: platform.id}})
            }
            //整合关系
            let insert = {};
            data.forEach(d => {
                let platform_id = d.platform_id;
                let daily_category_id = categories[d.categoryKey].id;
                let find = _.find(platformCategories, {platform_id, daily_category_id});
                if (find)
                    return;
                let key = platform_id + '_' + daily_category_id;
                if (!insert[key])
                    insert[key] = {platform_id, daily_category_id};
            });
            if (!_.isEmpty(insert)) {
                //插入新关系
                await DailyPlatformCategory.bulkCreate(Object.values(insert));
            }
            //重新获取所有关系
            platformCategories = await DailyPlatformCategory.findAll();
            platformCategories.forEach(pc => {
                let key = pc.platform_id + '_' + pc.daily_category_id;
                if (!associate[key])
                    associate[key] = pc.id;
            });
        }

        let finalData = {}, length;
        //整合数据
        {
            let repeat = 0;
            data.forEach(d => {
                if (!d)
                    return;
                const platform_id = d.platform_id;
                const daily_category_id = categories[d.categoryKey].id;
                const created_at = d.created_at;
                let key = platform_id + '_' + daily_category_id;
                const daily_platform_category_id = associate[key];
                key = created_at + '_' + key;

                if (!finalData[key])
                    finalData[key] = {
                        platform_id, daily_category_id, daily_platform_category_id,
                        watch: 0, open: 0, _new: 0, gift: 0, active: 0,
                        rank: d.rank, created_at: d.created_at,
                    };
                else repeat++;

                finalData[key].watch += d.watch;
                finalData[key].open += d.open;
                finalData[key]._new += d._new;
                finalData[key].active += d.active;
                finalData[key].gift += d.gift;
                if (finalData[key].rank > d.rank)
                    finalData[key].rank = d.rank;
            });
            finalData = Object.values(finalData);
            length = finalData.length;
            // console.log('数据预览', finalData[0]);
            // console.log('重复数量', repeat);
            // console.log('数据数量', length);
            if (length > 0)
                await DailyCategoryData.bulkCreate(finalData,
                    {updateOnDuplicate: ['watch', '_new', 'active', 'gift', 'open']});
        }
        return {got: length};
    }
}

module.exports = IResearchService;

/**
 * 从平台页面抓取页数
 * @param ctx
 * @param date
 * @param platform
 * @returns {Promise<{categories: {}, data: Array}>}
 */
async function getCategories(ctx, date, platform) {
    const firstPage = await ctx.curl(url(platform.platform, date, 1));
    const firstData = parse(firstPage.data, date, platform, true);
    if (isNaN(firstData.totalPage))
        return null;
    const categories = firstData.categories;
    let data = firstData.data;

    const q = queue((page, callback) => {
        getHTML(ctx, platform, date, page).then(res => {
            Object.assign(categories, res.categories);
            data = data.concat(res.data);
            callback();
        });
    }, 5);
    console.log('总页数', firstData.totalPage);
    for (let page = 2; page <= firstData.totalPage; page++) {
        q.push(page);
    }
    return new Promise(resolve => {
        q.drain = () => {
            resolve({categories, data});
        };
    });
}

/**
 * 获取网页内容
 * @param ctx
 * @param platform
 * @param date
 * @param page
 * @returns {Promise<{totalPage: number, categories: {}, data: Array}>}
 */
async function getHTML(ctx, platform, date, page) {
    const _url = url(platform.platform, date, page);
    // console.log({_url});
    const res = await ctx.curl(_url);
    return parse(res.data, date, platform);
}

/**
 * 组合成URL
 * @param platform
 * @param date
 * @param page
 * @returns {string}
 */
function url(platform, date, page) {
    return `http://www.iresearch.tv/research/platform/class/plat?platform=${platform}&date=${date}&field=lived&page=${page}`;
}

/**
 * 从HTML中提取数据
 * @param html
 * @param date
 * @param platform
 * @param needTotalPage {boolean}
 * @returns {{totalPage: number, categories: {}, data: Array}}
 */
function parse(html, date, platform, needTotalPage) {
    const $ = cheerio.load(html);
    const $list = $('tbody > tr');
    const categories = {}, data = [];
    const totalPage = needTotalPage ? parseInt($('ul.pagination li:nth-last-child(2) a').text().trim()) : 0;
    $list.each((i, e) => {
        const $e = $(e);
        const category = qj2bj($e.children('.nc').attr('title').trim()),
            categoryKey = category.toLowerCase(),
            active = parseInt($e.children('.bq').text().trim()),
            _new = parseInt($e.children('.sc').text().trim()),
            open = parseInt($e.children('.gz').text().trim()),
            watch = parseInt($e.children('.add-gz').text().trim()),
            rank = $e.children('.bt').text().trim();
        let gift = $e.children('.rq').text().trim();

        const match = gift.match(/(^[\d.]+)(千|万|亿)?\s(.+)?/);
        if (match) {
            const number = parseFloat(match[1]);
            if (isNaN(number))
                gift = 0;
            else {
                switch (match[2]) {
                    case '千':
                        gift = number * 1000;
                        break;
                    case '万':
                        gift = number * 10000;
                        break;
                    case '亿':
                        gift = number * 100000000;
                        break;
                    default:
                        gift = number;
                }
                gift = Math.round(gift);
            }
        } else {
            gift = 0;
        }
        if (!categories[categoryKey])
            categories[categoryKey] = {name: category};
        data.push({
            categoryKey,
            active,
            _new,
            open,
            gift,
            watch,
            rank,
            platform_id: platform.id,
            created_at: date,
        });
    });
    return {totalPage, categories, data};
}


/**
 * 半角符号 转 全角
 * @param str
 * @returns {string}
 * @constructor
 */
function qj2bj(str) {
    let tmp = "";
    for (let i = 0; i < str.length; i++) {
        if (str.charCodeAt(i) >= 65281 && str.charCodeAt(i) <= 65374) {// 如果位于全角！到全角～区间内
            tmp += String.fromCharCode(str.charCodeAt(i) - 65248)
        } else if (str.charCodeAt(i) === 12288) {//全角空格的值，它没有遵从与ASCII的相对偏移，必须单独处理
            tmp += ' ';
        } else {// 不处理全角空格，全角！到全角～区间外的字符
            tmp += str[i];
        }
    }
    return tmp;
}
